package com.example.core.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.core.dao.BaseDao;
import com.example.core.enums.SysEnum;
import com.example.core.exception.NORecordException;
import com.example.core.mapping.Mapping;
import com.example.core.po.BasePo;
import com.example.core.utils.EnumsUtils;
import com.example.core.utils.IdGenerator;
import com.example.core.vo.BaseVo;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

public abstract class BaseServiceImpl<M extends BaseDao<T>, T extends BasePo, V extends BaseVo> extends ServiceImpl<M, T> implements BaseService<T, V>, ApplicationContextAware {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    private ApplicationContext applicationContext;

    @Resource
    private DispatchService dispatchService;

    @Override
    public String add(T po) throws Exception {
        po.recalculateFields();
        dispatchService.save(po);
        saveOrUpdate(po);
        return po.getId();
    }

    @Override
    public String add(V vo) throws Exception {
        T po = toPo(vo);
        return add(po);
    }

    @Override
    public String update(V vo) throws Exception {
        T po = toPo(vo);
        po.recalculateFields();
        dispatchService.update(po);
        saveOrUpdate(po);
        return po.getId();
    }

    @Override
    public V view(String id) throws Exception {
        T po = getById(id);
        if (po == null) {
            throw new NORecordException(id);
        }
        return toVo(po);
    }

    @Override
    public void delete(String id) throws Exception {
        T po = getById(id);
        dispatchService.delete(po);
        removeById(id);
    }

    @Override
    public IPage<T> findPage(BaseVo vo, QueryWrapper<T> queryWrapper) {
        IPage<T> page = new Page<>(vo.getPageIndex(), vo.getPageSize());
        if (queryWrapper == null) {
            return page(page);
        }
        return page(page, queryWrapper);
    }

    @Override
    public T toPo(V vo) throws Exception {
        Mapping mapping = getMapping();
        T po = null;
        if (!StringUtils.isEmpty(vo.getId())) {
            po = getById(vo.getId());
        } else {
            String id = IdGenerator.generateStringId();
            po = (T) mapping.getPoClass().newInstance();
            po.setId(id);
        }
        BeanUtils.copyProperties(vo, po, this.getNullPropertyNames(vo));
        copyToPoEnums(po, vo);
        mapping.toPo(vo, po);
        dispatchService.toPo(po, vo);
        return po;
    }

    @Override
    public V toVo(T po) throws Exception {
        Mapping mapping = getMapping();
        Object vo = mapping.getVoClass().newInstance();
        BeanUtils.copyProperties(po, vo, this.getNullPropertyNames(po));
        copyToVoEnums(po, (V) vo);
        mapping.toVo(po, (BaseVo) vo);
        dispatchService.toVo(po, (BaseVo) vo);
        ((BaseVo<?>) vo).recalculateFields();
        return (V) vo;
    }

    @Override
    public Mapping getMapping() {
        return this.applicationContext.getBean(getMappingClass());
    }

    @Override
    public String[] getNullPropertyNames(Object source) {
        BeanWrapper srcWrapper = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = srcWrapper.getPropertyDescriptors();
        Set<String> emptyNames = new HashSet<>();
        for (PropertyDescriptor pd : pds) {
            Object srcValue = srcWrapper.getPropertyValue(pd.getName());
            if (srcValue == null || srcValue.equals("")) {
                emptyNames.add(pd.getName());
            }
        }
        return emptyNames.toArray(new String[emptyNames.size()]);
    }


    /***
     *
     * 把vo里面的枚举类型属性的值转换到po的枚举类型中
     * @param po
     * @param vo
     * @throws Exception
     */
    @Override
    public void copyToPoEnums(T po, V vo) throws Exception {
        for (Field field : FieldUtils.getAllFields(po.getClass())) {
            if (SysEnum.class.isAssignableFrom(field.getType())) {
                Object obj = FieldUtils.readDeclaredField(vo, field.getName(), true);
                if (obj != null) {
                    SysEnum sysEnum = EnumsUtils.getSysEnums((Class<SysEnum>) field.getType(), (String) obj);
                    FieldUtils.writeDeclaredField(po, field.getName(), sysEnum, true);
                }
            }
        }
    }

    /***
     *
     * 把PO的枚举值转成vo的普通属性
     * @param po
     * @param vo
     * @throws Exception
     */
    @Override
    public void copyToVoEnums(T po, V vo) throws Exception {
        for (Field field : FieldUtils.getAllFields(po.getClass())) {
            if (SysEnum.class.isAssignableFrom(field.getType())) {
                field.setAccessible(true);
                Object obj = field.get(po);
                if (obj != null) {
                    FieldUtils.writeDeclaredField(vo, field.getName(), ((SysEnum) obj).getValue(), true);
                    vo.getMap().put(field.getName(), ((SysEnum) obj).getLabel());
                }
            }
        }
    }

    @Override
    public Class<? extends BasePo> getPoClass() {
        return getMapping().getPoClass();
    }

    @Override
    public Class<? extends BaseVo> getVoClass() {
        return getMapping().getVoClass();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    @Override
    public Object getBean(Class clazz) {
        return getApplicationContext().getBean(clazz);
    }
}
